﻿using System;
using System.IO;
using System.Text;
using UnityEngine;

public enum EnumFileType
{
    ASSETS, // 包内资源.
    PERSISTENT, // SD卡资源.
    STREAMING_ASSETS // Streaming目录资源.
}

public class FileUtil
{
    private const string TEMP_FILE_SUFFIX = ".tmp";

    public static string GetUTF8String(byte[] buffer)
    {
        if (buffer == null)
            return null;

        if (buffer.Length <= 3)
        {
            return Encoding.UTF8.GetString(buffer);
        }

        byte[] bomBuffer = {0xef, 0xbb, 0xbf};

        if (buffer[0] == bomBuffer[0]
            && buffer[1] == bomBuffer[1]
            && buffer[2] == bomBuffer[2])
        {
            return new UTF8Encoding(false).GetString(buffer, 3, buffer.Length - 3);
        }

        return Encoding.UTF8.GetString(buffer);
    }

    /// <summary>
    ///     将文件从原始路径copy到目标路径.
    /// </summary>
    /// <param name="sourcePath">原始文件路径.不包括根目录.根目录由文件类型自动拼接.</param>
    /// <param name="sourceFileType">原始文件类型.</param>
    /// <param name="targetPath">目标文件路径.不包括根目录.根目录由文件类型自动拼接.</param>
    /// <param name="targetFileType">目标文件类型.</param>
    /// <returns></returns>
    public static bool CopyFile(string sourcePath, EnumFileType sourceFileType, string targetPath,
        EnumFileType targetFileType)
    {
        switch (targetFileType)
        {
            case EnumFileType.PERSISTENT:
            {
                var bytes = ReadFileToBytes(sourcePath, sourceFileType);
                SaveBinFile(targetPath, targetFileType, bytes);
                return true;
            }
                break;
            case EnumFileType.STREAMING_ASSETS:
#if UNITY_EDITOR || UNITY_IOS
            {
                var bytes = ReadFileToBytes(sourcePath, sourceFileType);
                SaveBinFile(targetPath, targetFileType, bytes);
                return true;
            }
#elif UNITY_ANDROID
                LogWrapper.LogError("[FileUtil][CopyFile] StreamingAssets must be editor or ios");
                return false;
#endif
                break;
            default:
                throw new ArgumentOutOfRangeException("[FileUtil][CopyFile]", targetFileType, null);
        }
    }

    /// <summary>
    ///     保存二进制到指定路径.
    /// </summary>
    public static void SaveBinFile(string targetPath, EnumFileType targetFileType, byte[] bytes)
    {
        var fullTargetPath = Path.Combine(GetRootPath(targetFileType), targetPath);
        var tmpPath = GetTempFilePath(fullTargetPath);
        LogWrapper.LogDebug("[FileUtil][SaveBinFile]fullTargetPath:" + fullTargetPath);
        switch (targetFileType)
        {
            case EnumFileType.PERSISTENT:
                try
                {
                    DeleteFile(tmpPath, targetFileType); // 删除临时文件,可能是以前存储时被中断遗留的.
                    CreateDirectoryByFileFullPath(tmpPath);
                    File.WriteAllBytes(tmpPath, bytes); // 保存到临时文件.
                    DeleteFile(fullTargetPath, targetFileType);
                    File.Move(tmpPath, fullTargetPath); // 改名.
                }
                catch (Exception e)
                {
                    LogWrapper.LogError("[FileUtil][SaveBinFile] EnumFileType.PERSISTENT, Exception:" + e.StackTrace);
                }

                break;
            case EnumFileType.STREAMING_ASSETS:
                try
                {
#if UNITY_EDITOR || UNITY_IOS
                    {
                        DeleteFile(tmpPath, targetFileType); // 删除临时文件,可能是以前存储时被中断遗留的.
                        CreateDirectoryByFileFullPath(tmpPath);
                        File.WriteAllBytes(tmpPath, bytes); // 保存到临时文件.
                        DeleteFile(fullTargetPath, targetFileType);
                        File.Move(tmpPath, fullTargetPath); // 改名.
                    }
#elif UNITY_ANDROID
                    LogWrapper.LogError("[FileUtil][SaveBinFile] StreamingAssets must be editor or ios");
#endif
                }
                catch (Exception e)
                {
                    LogWrapper.LogError("[FileUtil][SaveBinFile] EnumFileType.STREAMING_ASSETS, Exception:" +
                                        e.StackTrace);
                }

                break;
            default:
                throw new ArgumentOutOfRangeException("targetFileType", targetFileType, null);
        }
    }

    /// <summary>
    ///     保存普通文本.
    /// </summary>
    public static void SaveTextFile(string targetPath, EnumFileType targetFileType, string contents)
    {
        var fullTargetPath = Path.Combine(GetRootPath(targetFileType), targetPath);
        var tmpPath = GetTempFilePath(fullTargetPath);
        LogWrapper.LogDebug("[FileUtil][SaveTextFile]fullTargetPath:" + fullTargetPath);
        switch (targetFileType)
        {
            case EnumFileType.PERSISTENT:
                try
                {
                    DeleteFile(tmpPath, targetFileType); // 删除临时文件,可能是以前存储时被中断遗留的.
                    CreateDirectoryByFileFullPath(tmpPath);
                    File.WriteAllText(tmpPath, contents); // 保存到临时文件.
                    DeleteFile(fullTargetPath, targetFileType);
                    File.Move(tmpPath, fullTargetPath); // 改名.
                }
                catch (Exception e)
                {
                    LogWrapper.LogError("[FileUtil][SaveTextFile] Exception:" + e.StackTrace);
                }

                break;
            default:
                throw new ArgumentOutOfRangeException("targetFileType", targetFileType, null);
        }
    }

    /// <summary>
    ///     读取文件为二进制.
    /// </summary>
    public static byte[] ReadFileToBytes(string path, EnumFileType fileType)
    {
        switch (fileType)
        {
            case EnumFileType.ASSETS:
                return ReadAssetsFileToBytes(path);
            case EnumFileType.PERSISTENT:
                return ReadPersistentFileToBytes(path);
            case EnumFileType.STREAMING_ASSETS:
                return ReadStreamingAssetsFileToBytes(path);
            default:
                throw new ArgumentOutOfRangeException("fileType", fileType, null);
        }
    }

    /// <summary>
    ///     读取包外资源.
    /// </summary>
    private static byte[] ReadPersistentFileToBytes(string path)
    {
        byte[] bytes = null;
        var fullPath = Path.Combine(GetRootPath(EnumFileType.PERSISTENT), path);
        try
        {
            if (File.Exists(fullPath))
            {
                bytes = File.ReadAllBytes(fullPath);
            }
            else
            {
                LogWrapper.LogError("[FileUtil][ReadPersistentFileToBytes]File Not Exist:", fullPath);
            }
        }
        catch (Exception e)
        {
            LogWrapper.LogError("[FileUtil][ReadPersistentFileToBytes] Exception:", e.StackTrace);
        }

        return bytes;
    }

    /// <summary>
    ///     读包内资源.
    /// </summary>
    private static byte[] ReadAssetsFileToBytes(string path)
    {
        LogWrapper.LogError("没有实现读取包内资源的方法:", path);
        return null;
    }

    /// <summary>
    ///     读取StreamingAsset资源.
    /// </summary>
    /// <param name="path">路径不包括StreamingAssets部分.</param>
    private static byte[] ReadStreamingAssetsFileToBytes(string path)
    {
        byte[] bytes = null;
        var fullPath = Path.Combine(GetRootPath(EnumFileType.STREAMING_ASSETS), path);
        try
        {
#if UNITY_EDITOR || UNITY_IPHONE
            if (File.Exists(fullPath))
            {
                bytes = File.ReadAllBytes(fullPath);
            }
            else
            {
                LogWrapper.LogError("[FileUtil][ReadStreamingAssetsFileToBytes]File Not Exist:", fullPath);
            }

#elif UNITY_ANDROID
            if (fullPath.Contains("://") || fullPath.Contains(":///"))
            {
                var reader = new WWW(fullPath);
                while (!reader.isDone)
                {
                }

                bytes = reader.bytes;
            }
            else
            {
                LogWrapper.LogError("[FileUtil][ReadStreamingAssetsFileToBytes]File Path Error:", fullPath);
            }
#endif
        }
        catch (Exception e)
        {
            LogWrapper.LogError("[FileUtil][ReadStreamingAssetsFileToBytes] Exception:", e.StackTrace);
        }

        return bytes;
    }

    /// <summary>
    ///     读取文件为字符串.
    /// </summary>
    public static string ReadFileToString(string path, EnumFileType fileType)
    {
        var bytes = ReadFileToBytes(path, fileType);
        if (bytes == null)
        {
            return string.Empty;
        }

        return GetUTF8String(bytes);
    }

    /// <summary>
    ///     获取临时路径.
    /// </summary>
    private static string GetTempFilePath(string path)
    {
        return path + TEMP_FILE_SUFFIX;
    }

    private static void MakeFileWriteAble(string path, EnumFileType fileType)
    {
        if (string.IsNullOrEmpty(path))
        {
            LogWrapper.LogError("[FileUtil][MakeFileWriteable] filePath is null or empty");
            return;
        }

        switch (fileType)
        {
            case EnumFileType.PERSISTENT:
                if (File.Exists(path))
                {
                    File.SetAttributes(path, File.GetAttributes(path) & ~FileAttributes.Hidden);
                    File.SetAttributes(path,
                        File.GetAttributes(path) & ~(FileAttributes.Archive | FileAttributes.ReadOnly));
                }
                break;
            case EnumFileType.STREAMING_ASSETS:
#if UNITY_EDITOR || UNITY_IOS
                if (File.Exists(path))
                {
                    File.SetAttributes(path, File.GetAttributes(path) & ~FileAttributes.Hidden);
                    File.SetAttributes(path,
                        File.GetAttributes(path) & ~(FileAttributes.Archive | FileAttributes.ReadOnly));
                }
#elif UNITY_ANDROID
                    LogWrapper.LogError("[FileUtil][MakeFileWriteAble] StreamingAssets must be editor or ios");
#endif
                break;
            default:
                throw new ArgumentOutOfRangeException("fileType", fileType, null);
        }
    }

    /// <summary>
    ///     删除文件.
    /// </summary>
    public static void DeleteFile(string path, EnumFileType fileType)
    {
        switch (fileType)
        {
            case EnumFileType.PERSISTENT:
                if (File.Exists(path))
                {
                    MakeFileWriteAble(path, fileType);
                    File.Delete(path);
                }
                break;
            case EnumFileType.STREAMING_ASSETS:
#if UNITY_EDITOR || UNITY_IOS
                    if (File.Exists(path))
                    {
                        MakeFileWriteAble(path, fileType);
                        File.Delete(path);
                    }
#elif UNITY_ANDROID
                    LogWrapper.LogError("[FileUtil][DeleteFile] StreamingAssets must be editor or ios");
#endif
                break;
            default:
                throw new ArgumentOutOfRangeException("fileType", fileType, null);
        }
    }

    /// <summary>
    ///     创建目录.
    /// </summary>
    public static void CreateDirectory(string path)
    {
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
    }

    /// <summary>
    ///     根据文件创建文件所在目录.
    /// </summary>
    public static void CreateDirectoryByFileFullPath(string fullPath)
    {
        var path = Path.GetDirectoryName(fullPath);
        CreateDirectory(path);
    }

    /// <summary>
    ///     判断文件是否存在.
    /// </summary>
    /// <param name="path">文件路径.不包括根目录.根目录由文件类型自动拼接.</param>
    /// <param name="fileType">文件类型.</param>
    /// <returns></returns>
    public static bool CheckFileExist(string path, EnumFileType fileType)
    {
        string fullPath = string.Empty;
        switch (fileType)
        {
            case EnumFileType.PERSISTENT:
                fullPath = Path.Combine(GetRootPath(fileType), path);
                return File.Exists(fullPath);
            case EnumFileType.STREAMING_ASSETS:
                fullPath = Path.Combine(GetRootPath(fileType), path);
#if UNITY_EDITOR || UNITY_IPHONE
                return File.Exists(fullPath);
#elif UNITY_ANDROID
            if (fullPath.Contains("://") || fullPath.Contains(":///"))
            {
                var reader = new WWW(fullPath);
                LogWrapper.LogInfo("[FileUtil][CheckFileExist] reader.error:", reader.error);
                return string.IsNullOrEmpty(reader.error);
            }
            else
            {
                LogWrapper.LogError("[FileUtil][ReadStreamingAssetsFileToBytes]File Path Error:", fullPath);
                return false;
            }
#endif
            default:
                throw new ArgumentOutOfRangeException("fileType", fileType, null);
        }
    }

    /// <summary>
    ///     根据文件类型获取文件根目录.
    /// </summary>
    /// <param name="fileType">
    ///     ASSETS:D:/workspace_git/Unity/Unity5.4.5_NGUI/Assets
    ///     PERSISTENT:Android/com.zy.test/File
    ///     STREAMING_ASSETS:可以将apk解压.StreamingAssets中的文件会在解压后的assets文件夹下.
    /// </param>
    public static string GetRootPath(EnumFileType fileType)
    {
        switch (fileType)
        {
            case EnumFileType.ASSETS:
                return Application.dataPath;
            case EnumFileType.PERSISTENT:
#if UNITY_EDITOR
                return Application.dataPath + "/../Persistent/";
#else
                return Application.persistentDataPath + "/";
#endif
            case EnumFileType.STREAMING_ASSETS:
                return Application.streamingAssetsPath;
            default:
                throw new ArgumentOutOfRangeException("fileType", fileType, null);
        }
    }
}